package com.ihelp.rest.base.hibernate4;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.persistence.Id;

import org.apache.commons.lang.StringUtils;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.transform.Transformers;
import org.hibernate.type.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.util.Assert;

import com.ihelp.rest.base.IBaseDao;
import com.ihelp.rest.base.po.Page;


public abstract class BaseHibernateDao<M extends Serializable, PK extends Serializable> implements IBaseDao<M, PK> {

    protected static final Logger LOGGER = LoggerFactory.getLogger(BaseHibernateDao.class);

    private final Class<M> entityClass;
    private final String HQL_LIST_ALL;
    private final String HQL_COUNT_ALL;
    private final String HQL_OPTIMIZE_PRE_LIST_ALL;
    private final String HQL_OPTIMIZE_NEXT_LIST_ALL;
    private String pkName = null;

    @SuppressWarnings("unchecked")
    public BaseHibernateDao() {
        this.entityClass = (Class<M>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        Field[] fields = this.entityClass.getDeclaredFields();
        for(Field f : fields) {
            if(f.isAnnotationPresent(Id.class)) {
                this.pkName = f.getName();
            }
        }
        
        //TODO @Entity name not null
        HQL_LIST_ALL = "from " + this.entityClass.getSimpleName() + " order by " + pkName + " desc";
        HQL_OPTIMIZE_PRE_LIST_ALL = "from " + this.entityClass.getSimpleName() + " where " + pkName + " > ? order by " + pkName + " asc";
        HQL_OPTIMIZE_NEXT_LIST_ALL = "from " + this.entityClass.getSimpleName() + " where " + pkName + " < ? order by " + pkName + " desc";
        HQL_COUNT_ALL = " select count(*) from " + this.entityClass.getSimpleName();
    }
        
    @Autowired
    @Qualifier("sessionFactory")
    private SessionFactory sessionFactory;

    public Session getSession() {
        //事务必须是开启的(Required)，否则获取不到
        return sessionFactory.getCurrentSession();
    }
    
    @SuppressWarnings("unchecked")
    public PK save(M model) {
        return (PK) getSession().save(model);
    }

    public void saveOrUpdate(M model) {
        getSession().saveOrUpdate(model);
    }

    public void update(M model) {
        getSession().update(model);
    }

    public void merge(M model) {
        getSession().merge(model);
    }

    public void delete(PK id) {
        getSession().delete(this.get(id));
    }

    public void deleteObject(M model) {
        getSession().delete(model);
    }

    public boolean exists(PK id) {
        return get(id) != null;
    }

    @SuppressWarnings("unchecked")
    public M get(PK id) {
        return (M) getSession().get(this.entityClass, id);
    }

    public int countAll() {
        Long total = aggregate(HQL_COUNT_ALL);
        return total.intValue();
    }
    
    public int countAllParam(String hql, Object... paramlist) {
        Long total = aggregate(hql, paramlist);
        return total.intValue();
    }

    public List<M> listAll() {
        return list(HQL_LIST_ALL);
    }

    public List<M> listAll(int pn, int pageSize) {
        return list(HQL_LIST_ALL, pn, pageSize);
    }
    
    public List<M> pre(PK pk, int pn, int pageSize) {
        if(pk == null) {
            return list(HQL_LIST_ALL, pn, pageSize);
        }
        //倒序，重排
        List<M> result = list(HQL_OPTIMIZE_PRE_LIST_ALL, 1, pageSize, pk);
        Collections.reverse(result);
        return result;
    }
    
    public List<M> next(PK pk, int pn, int pageSize) {
        if(pk == null) {
            return list(HQL_LIST_ALL, pn, pageSize);
        }
        return list(HQL_OPTIMIZE_NEXT_LIST_ALL, 1, pageSize, pk);
    }
    
    public void flush() {
        getSession().flush();
    }

    public void clear() {
        getSession().clear();
    }

    protected long getIdResult(String hql, Object... paramlist) {
        long result = -1;
        List<?> list = list(hql, paramlist);
        if (list != null && list.size() > 0) {
            return ((Number) list.get(0)).longValue();
        }
        return result;
    }

    protected List<M> listSelf(final String hql, final int pn, final int pageSize, final Object... paramlist) {
        return this.<M> list(hql, pn, pageSize, paramlist);
    }
    
    /**
     * 分页
     * @param <T>
     * @param hql
     * @param pn
     * @param pageSize
     * @param paramlist
     * @return
     */
    @SuppressWarnings("unchecked")
	public <T> Page<T> page(String hql, int pn, int pageSize, Object... paramlist) {
    	if(StringUtils.isBlank(hql)){
    		hql = HQL_LIST_ALL;
    	}
    	Page<T> page = new Page<T>();
        Query query = getSession().createQuery(hql);
        setParameters(query, paramlist);
        if(pn > -1 && pageSize > -1){
        	int start = (pn - 1) * pageSize;
            query.setFirstResult(start);
            query.setMaxResults(pageSize);
        }
        List<T> results = query.list();
        page.setItems(results);
        page.setTotalCount(generatePageTotalCount(hql, paramlist));
        return page;
    }
    
    
    /**
	 * 按sql进行分页查询.
	 * @param <T>
	 * @param page 分页参数.不支持其中的orderBy参数.
	 * @param sql sq语句.
	 * @param cl maping对象.
	 * @param values 数量可变的查询参数,按顺序绑定.
	 * @return 分页查询结果, 附带结果列表及所有查询时的参数.
	 */
	public <T> Page <T> findSqlPage(final Page<T> page, final String sql, final Class cl, final Object... values) {
		Assert.notNull(page, "page不能为空"); 
		SQLQuery q = createSqlQuery(sql, values);
			long totalCount = countSqlResult(sql, values);
			page.setTotalCount((int)totalCount);
		setSqlPageParameter(q, page);
		List result = q.setResultTransformer(Transformers.aliasToBean(cl)).list();
		page.setItems(result);
		return page;
	}
	
	/**
	 * 设置分页参数到SqlQuery对象,辅助函数.
	 * @param <T>
	 */
	protected <T> Query setSqlPageParameter(final SQLQuery q, final Page<T> page) {
		//hibernate的firstResult的序号从0开始
		q.setFirstResult(page.getFirstOfPage() - 1);
		q.setMaxResults(page.getPageSize());
		return q;
	}
	
	/**
	 * 执行count查询获得本次sql查询所能获得的对象总数.
	 * 本函数只能自动处理简单的sql语句,复杂的sql查询请另行编写count语句查询.
	 */
	public long countSqlResult(final String sql, final Object... values) {
		Long count = 0L;
		String fromHql = sql;
		//select子句与order by子句会影响count查询,进行简单的排除.
		fromHql = "from " + StringUtils.substringAfter(fromHql, "from");
		fromHql = StringUtils.substringBefore(fromHql, "order by");

		String countHql = "select count(*) " + fromHql;

		try {
		 count = ((BigDecimal) findSqlUnique(countHql, values)).longValue();
		} catch (Exception e) {
			throw new RuntimeException("hql can't be auto count, sql is:" + countHql, e);
		}
		return count;
	}
	
	
	/**
	 * 按SQL查询唯一对象.
	 * 
	 * @param values 数量可变的参数,按顺序绑定.
	 */
	public <X> X findSqlUnique(final String sql, final Object... values) {
		return (X) createSqlQuery(sql, values).uniqueResult();
	}
	/**
	 * 根据查询SQL与参数列表创建Query对象.
	 * 本类封装的find()函数全部默认返回对象类型为T,当不为T时使用本函数.
	 * 
	 * @param values 数量可变的参数,按顺序绑定.
	 */
	public SQLQuery createSqlQuery(final String queryString, final Object... values) {
		Assert.hasText(queryString, "queryString不能为空");
		SQLQuery query = getSession().createSQLQuery(queryString);
		if (values != null) {
			for (int i = 0; i < values.length; i++) {
				query.setParameter(i, values[i]);
			}
		}
		return query;
	}
	
    /**
     * 查询数count统计
     * @param hql
     * @param paramlist
     * @return
     */
    public int generatePageTotalCount(String hql, Object... paramlist){
    	String generatedCountHql = null;
    	if(hql.contains("select")){
    		generatedCountHql = "select count(*) from " + StringUtils.substringAfter(hql, "from");
    		generatedCountHql = StringUtils.substringAfter(generatedCountHql, "order");
    	}
    	else{
    		generatedCountHql = "select count(*) " + StringUtils.substringBefore(hql, "order");
    	}
    	return countAllParam(generatedCountHql, paramlist);
    }

    /**
     * for in
     */
    @SuppressWarnings("unchecked")
    protected <T> List<T> listWithIn(final String hql,final int start, final int length, final Map<String, Collection<?>> map, final Object... paramlist) {
        Query query = getSession().createQuery(hql);
        setParameters(query, paramlist);
        for (Entry<String, Collection<?>> e : map.entrySet()) {
            query.setParameterList(e.getKey(), e.getValue());
        }
        if (start > -1 && length > -1) {
            query.setMaxResults(length);
            if (start != 0) {
                query.setFirstResult(start);
            }
        }
        List<T> results = query.list();
        return results;
    }

    @SuppressWarnings("unchecked")
    protected <T> List<T> list(final String hql, final int pn, final int pageSize, final Object... paramlist) {
        Query query = getSession().createQuery(hql);
        setParameters(query, paramlist);
        if(pn > -1 && pageSize > -1){
        	int start = (pn - 1) * pageSize;
            query.setFirstResult(start);
            query.setMaxResults(pageSize);
        }
        List<T> results = query.list();
        return results;
    }

    /**
     * 根据查询条件返回唯一一条记录
     */
    @SuppressWarnings("unchecked")
    protected <T> T unique(final String hql, final Object... paramlist) {
        Query query = getSession().createQuery(hql);
        setParameters(query, paramlist);
        return (T) query.setMaxResults(1).uniqueResult();
    }

   /**
    * for in
    */
    @SuppressWarnings("unchecked")
    protected <T> T aggregate(final String hql, final Map<String, Collection<?>> map, final Object... paramlist) {
        Query query = getSession().createQuery(hql);
        if (paramlist != null) {
            setParameters(query, paramlist);
            for (Entry<String, Collection<?>> e : map.entrySet()) {
                query.setParameterList(e.getKey(), e.getValue());
            }
        }
        return (T) query.uniqueResult();
    }
        
    @SuppressWarnings("unchecked")
    protected <T> T aggregate(final String hql, final Object... paramlist) {
        Query query = getSession().createQuery(hql);
        setParameters(query, paramlist);
        return (T) query.uniqueResult();
    }

    /**
     * 执行批处理语句.如 之间insert, update, delete 等.
     */
    protected int execteBulk(final String hql, final Object... paramlist) {
        Query query = getSession().createQuery(hql);
        setParameters(query, paramlist);
        Object result = query.executeUpdate();
        return result == null ? 0 : ((Integer) result).intValue();
    }
    
    protected int execteNativeBulk(final String natvieSQL, final Object... paramlist) {
        Query query = getSession().createSQLQuery(natvieSQL);
        setParameters(query, paramlist);
        Object result = query.executeUpdate();
        return result == null ? 0 : ((Integer) result).intValue();
    }

    protected <T> List<T> list(final String sql, final Object... paramlist) {
        return list(sql, -1, -1, paramlist);
    }
        
    @SuppressWarnings("unchecked")
    protected <T> T aggregateByNative(final String natvieSQL, final List<Entry<String, Type>> scalarList, final Object... paramlist) {
        SQLQuery query = getSession().createSQLQuery(natvieSQL);
        if (scalarList != null) {
            for (Entry<String, Type> entity : scalarList) {
                query.addScalar(entity.getKey(), entity.getValue());
            }
        }
        setParameters(query, paramlist);
        Object result = query.uniqueResult();
        return (T) result;
    }
        
    @SuppressWarnings("unchecked")
    public <T> List<T> list(Criteria criteria) {
        return criteria.list();
    }

    @SuppressWarnings("unchecked")
    public <T> T unique(Criteria criteria) {
        return (T) criteria.uniqueResult();
    }

    public <T> List<T> list(DetachedCriteria criteria) {
        return list(criteria.getExecutableCriteria(getSession()));
    }

    @SuppressWarnings("unchecked")
    public <T> T unique(DetachedCriteria criteria) {
        return (T) unique(criteria.getExecutableCriteria(getSession()));
    }

    protected void setParameters(Query query, Object[] paramlist) {
        if (paramlist != null) {
            for (int i = 0; i < paramlist.length; i++) {
                if(paramlist[i] instanceof Date) {
                    query.setTimestamp(i, (Date)paramlist[i]);
                } else {
                    query.setParameter(i, paramlist[i]);
                }
            }
        }
    }
        
}
